#pragma once
#include <iostream>
#include <functional>
#include <string>
#include <sys/types.h> /* See NOTES */
#include <sys/socket.h>
#include <errno.h>
#include <cstring>
#include <arpa/inet.h>
#include <sys/socket.h>
#include <sys/types.h>
namespace Server
{
    using namespace std;
    static const string defaultIp = "0.0.0.0";
    static const int gnum = 1024;

    enum
    {
        USAGE_ERR = 1,
        SOCKET_ERR,
        BIND_ERR,
        OPEN_ERR
    };

    typedef function<void(int,string, uint16_t, string)> func_t;

    class UdpServer
    {
    public:
        UdpServer(const func_t &cb, const uint16_t &port, const string &ip = defaultIp)
            : _callback(cb), _port(port), _ip(ip), _sockfd(-1)
        {
        }

        void initServer()
        {
            // int socket(int domain, int type, int protocol);
            // 第一个参数的使用ipv4
            // 第二个参数是SOCK_DGRAM：这指定了套接字的类型。SOCK_DGRAM 表示套接字是数据报类型，适用于无连接的数据包服务。使用 UDP（User Datagram Protocol，用户数据报协议）时，会选择这种类型。
            _sockfd = socket(AF_INET, SOCK_DGRAM, 0);
            if (_sockfd == -1)
            {
                cerr << "socket error:" << errno << " : " << strerror(errno) << endl;
                exit(SOCKET_ERR);
            }
            cout << "socket success ..." << _sockfd << endl;

            // 接下来的步骤是绑定端口号
            struct sockaddr_in local;
            local.sin_family = AF_INET;
            // typedef uint16_t in_port_t; 这是定义
            // 定义这个结构体的目标是 ，我们发信息给别人，我们的ip和port也要发送给对方
            // 我们在网络传输的时候，是用的大端传输，这个函数会帮我们转换，如果已经是大端了，就不会做任何事情，如果不是，就会帮我们转换成大端 的形式
            local.sin_port = htons(_port);
            // in_addr_t inet_addr(const char *cp);函数接口
            // typedef uint32_t in_addr_t;
            // struct in_addr
            // {
            //     in_addr_t s_addr;
            // };

            // 发现这个封装好的函数返回值正好是我们想要的 ，解释一下这里操作的本质
            // 我们传入的ip是点分十进制，"111.111.111.11"形式，但是我们定义ip的时候时，使用的32位的整数typedef uint32_t in_addr_t;，这个函数会帮我们做这个事情，并同时用大端传输

            // 之前一直搞不懂为什么要用c——str函数，命名都是字符串，其实c语言里面没有字符串类型，我们用的类型是string 但是 c语言的字符串是 const char* 类型 。是以\0结尾的。
            //  因为string类型里面还有size ，length这些属性，但是c语言的里面没有。
            local.sin_addr.s_addr = inet_addr(_ip.c_str()); // 1. string->uint32_t 2. htonl(); -> inet_addr
                                                            // local.sin_addr.s_addr = htonl(INADDR_ANY); // 任意地址bind，服务器的真实写法

            // 开始绑定
            int result = bind(_sockfd, (struct sockaddr *)(&local), sizeof(local));
            if (result == -1)
            {
                cerr << "bind error:" << errno << " : " << strerror(errno) << endl;
                exit(BIND_ERR);
            }
        }

        void start()
        {
            // 服务器的本质其实就是一个死循环
            char buffer[gnum];
            // 死循环的另外的定义方法
            for (;;)
            {
                // 读取数据
                //   ssize_t recvfrom(int sockfd, void *buf, size_t len, int flags,struct sockaddr *src_addr, socklen_t *addrlen);
                struct sockaddr_in peer;
                socklen_t len = sizeof(peer);
                // 读取数据
                // 0是阻塞式读取
                ssize_t s = recvfrom(_sockfd, buffer, sizeof(buffer) - 1, 0, (struct sockaddr *)&peer, &len);

                if (s > 0)
                {
                    buffer[s] = 0;
                    // extern char *inet_ntoa (struct in_addr __in) __THROW; 这里有一个疑问 ，为什么和之前的形式不一样，.到结构体最里面的一个是local.sin_addr.s_addr=inet_addr(_ip.c_str());这里具体是看
                    // 函数的参数，这里的参数就是结构体，效果跟上面是相反的
                    string clientIp = inet_ntoa(peer.sin_addr); // 1. 网络序列 2. int->点分十进制IP
                    uint16_t clientPort = ntohs(peer.sin_port);
                    string messages = buffer;
                    cout << clientIp << "[" << clientPort << "]# " << messages << endl;
                    _callback(_sockfd,clientIp, clientPort, messages);
                }
            }
        }

        ~UdpServer()
        {
        }

    private:
        uint16_t _port;
        string _ip;
        int _sockfd;
        func_t _callback;
    };
}
